﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Tradex.Market {

    /// <summary>
    /// The InstrumentPricing class contains allowed price information for an instrument,
    /// inthe form of a number of InstrumentPricing items. These define the allowed pricing steps
    /// within a specific price band. As not all instruments have the same pricing granularity
    /// over the complete band (example: options often have a much lower increment around their
    /// zero value, then switch to higher increments futher away from zero).
    /// </summary>
    [Serializable ()]
    public class InstrumentPricing {

        /// <summary>
        /// Constructor taking an enumerable source of items. We do not take anything more
        /// specific as the method performs a sort anyway to make sure the guaranteed order
        /// is enforced.
        /// </summary>
        /// <param name="items"></param>
        public InstrumentPricing (
            IEnumerable<InstrumentPricingItem> items
        ) {
            _Items = items.OrderBy(a => a.MinimumPrice).ToArray();
            InstrumentPricingItem item = _Items.OrderBy(a => a.Increment).FirstOrDefault();
            if (item != null) {
                Increment = item.Increment;
                Precision = item.Precision;
            }
        }

        /// <summary>
        /// Constructor parsing a string encoding the pricing information.
        /// </summary>
        /// <param name="pricing"></param>
        public InstrumentPricing(string pricing) {
            Contract.Assert(pricing != null);
            if (pricing == null) {
                throw new ArgumentException("pricing can not be a null string", "pricing");
            }
            string[] seconds = pricing.Split(';');
            List<InstrumentPricingItem> items = new List<InstrumentPricingItem>();
            foreach (string second in seconds) {
                if (String.IsNullOrWhiteSpace(second)) {
                    continue;
                }
                InstrumentPricingItem item = new InstrumentPricingItem(second);
                items.Add(item);
            }
            _Items = items.OrderBy(a => a.MinimumPrice).ToArray();
            InstrumentPricingItem item2 = _Items.OrderBy(a => a.Increment).FirstOrDefault();
            if (item2 != null) {
                Increment = item2.Increment;
                Precision = item2.Precision;
            }
        }

        /// <summary>
        /// Accessor allowing retrieval of a specific price band via index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public InstrumentPricingItem this[int index] {
            get { return _Items[index]; }
        }

        /// <summary>
        /// Gets an enumerator of the pricing bands. These are sorted from the lowest possible
        /// price upward.
        /// </summary>
        public IEnumerable<InstrumentPricingItem> Items {
            get {
                foreach (InstrumentPricingItem item in _Items) {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Gets the allowed increment. This is the smallest increment in all items.
        /// </summary>
        public double Increment { get; private set; }

        /// <summary>
        /// Gets the allowed precision, being the highest precision in all items.
        /// </summary>
        public byte Precision { get; private set; }

        private InstrumentPricingItem[] _Items;

        /// <summary>
        /// Formats a price as a string in the given precision.
        /// </summary>
        /// <param name="price"></param>
        /// <returns></returns>
        public string FormatPrice(double price) {
            string symbolstring = new String('#', Precision);
            string formattedPrice = (Double.IsNegativeInfinity(price)) ? "NaN" : String.Format(CultureInfo.InvariantCulture, "{0:0." + symbolstring + "}", price);
            return formattedPrice;
        }

        /// <inheritdoc />
        public override string ToString() {
            StringBuilder sb = new StringBuilder();
            //sb.Append(Increment);
            //sb.Append("/");
            //sb.Append(Precision);
            //sb.Append(":");
            foreach (InstrumentPricingItem item in _Items) {
                if (sb.Length != 0) {
                    sb.Append(";");
                }
                sb.Append(item.ToString());
            }
            return sb.ToString();
        }

        /// <summary>
        /// Returns an array with the next X valid prices from a given start, going upward.
        /// </summary>
        /// <param name="initialPrice"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public IEnumerable<double> GetValidUpticks(double initialPrice, int amount) {
            //Lets find the valid start price. This is our initialPrice, of the next higher Tick.
            double ticks = initialPrice / Increment;
            ticks = Math.Round(ticks, 0, MidpointRounding.AwayFromZero);
            while (amount != 0) {
                double price = ticks * Increment;
                yield return price;
                amount--;
                ticks++;
            }
        }

        /// <summary>
        /// Returns an array with the next x valid prices from a given start, going dowward.
        /// </summary>
        /// <param name="initialPrice"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public IEnumerable<double> GetValidDownticks(double initialPrice, int amount) {
            //Lets find the valid start price. This is our initialPrice, of the next higher Tick.
            double ticks = initialPrice / Increment;
            ticks = Math.Round(ticks, 0, MidpointRounding.AwayFromZero);
            while (amount != 0) {
                double price = ticks * Increment;
                yield return price;
                amount--;
                ticks--;
            }
        }

        /// <summary>
        /// Compares the content to another InstrumentPricing.
        /// </summary>
        /// <param name="otherItem"></param>
        /// <returns></returns>
        public bool EqualContent(InstrumentPricing otherItem) {
            if (otherItem == null) {
                return false;
            }
            if (
                Increment != otherItem.Increment ||
                Precision != otherItem.Precision ||
                ToString () != otherItem.ToString ()
            ) {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Checks whether this InstrumentPricing is an Update for another InstrumentPricing.
        /// </summary>
        /// <param name="otherItem"></param>
        /// <returns></returns>
        public bool IsUpdate(InstrumentPricing otherItem) {
            if (otherItem == null) {
                return false;
            }
            if (
                Increment != otherItem.Increment ||
                Precision != otherItem.Precision ||
                ToString() != otherItem.ToString()
            ) {
                return false;
            }
            return true;
        }

    }

}
